#pragma once

#include <zenox/glm.hpp>
#include <zenoui/Rect.hpp>
#include <zenoui/Enums.hpp>
#include <zenoui/Transform.hpp>
#include <zenox/memory.hpp>
#include <type_traits>
#include <vector>
#include <string>

namespace zenoui {

struct Widget;
struct Game;

struct Event {
public:
    Event() = default;
    Event(Event &&) = delete; // disable move and copy
    ~Event() = default;

    void accept(Widget *by_widget) { // stops event from recursively propgating into child widgets
        m_accepted = zenox::weak_from(by_widget);
    }

    virtual void send_to(Widget *to_widget) = 0; // will be automatically implemented by EventCRTP

    glm::vec2 pos() const { // get local position coordinate
        return m_transform.abs_from(m_pos);
    }

    glm::vec2 const &global_pos() const { // get global (screen space) position coordinate
        return m_pos;
    }

    Transform &transform() {
        return m_transform;
    }

    Transform const &transform() const {
        return m_transform;
    }

    zenox::weak_ptr<Widget> accepted() const {
        return m_accepted;
    }

private:
    zenox::weak_ptr<Widget> m_accepted;
    glm::vec2 m_pos; // mouse position when event was occured

    friend Game;

protected:
    Transform m_transform; // local/world transformation needed to be applied on the m_pos
};

template <class DerivedEvent>
struct EventCRTP : Event {
    void send_to(Widget *to_widget) override; // implemented later in Widget.hpp
};

struct MouseClickEvent : EventCRTP<MouseClickEvent> {
private:
    MouseButton m_button;
    KeyModifiers m_mods;

    friend Game;

public:
    MouseButton button() const {
        return m_button;
    }

    KeyModifiers mods() const {
        return m_mods;
    }
};

struct MouseReleaseEvent : EventCRTP<MouseReleaseEvent> {
private:
    MouseButton m_button;
    KeyModifiers m_mods;

    friend Game;

public:
    MouseButton button() const {
        return m_button;
    }

    KeyModifiers mods() const {
        return m_mods;
    }
};

struct MouseMoveEvent : EventCRTP<MouseMoveEvent> {
private:
    MouseButton m_button;
    glm::vec2 m_delta_pos;

    friend Game;

public:
    MouseButton button() const {
        return m_button;
    }

    glm::vec2 delta_pos() const { // get local motion vector coordinate
        return m_transform.rel_from(m_delta_pos);
    }

    glm::vec2 const &global_delta_pos() const { // get global (screen space) motion vector coordinate
        return m_delta_pos;
    }
};

struct MouseHoverEvent : EventCRTP<MouseHoverEvent> {
private:
    enum HoverType {
        HoverDetect, // fake pass, only meant to be accepted
        HoverEnter,
        HoverLeave,
    };
    HoverType m_hover_type = HoverDetect;

    friend Game;

public:
    bool is_enter() const { // when mouse entered widget's rect
        return m_hover_type == HoverEnter;
    }

    bool is_leave() const { // when mouse leaved widget's rect
        return m_hover_type == HoverLeave;
    }
};

struct MouseWheelEvent : EventCRTP<MouseWheelEvent> {
private:
    glm::vec2 m_delta;

    friend Game;

public:
    glm::vec2 delta_wheel() const {
        return m_delta;
    }

    /* glm::vec2 delta_wheel_norminal() const { */
    /*     return { */
    /*         m_delta.x < 0 ? -1 : m_delta.x > 0 ? 1 : 0, */
    /*         m_delta.y < 0 ? -1 : m_delta.y > 0 ? 1 : 0, */
    /*     }; */
    /* } */
};

struct KeyPressEvent : EventCRTP<KeyPressEvent> {
private:
    int m_key;
    KeyModifiers m_mods;

    friend Game;

public:
    int key() const {
        return m_key;
    }

    KeyModifiers mods() const {
        return m_mods;
    }
};

struct KeyReleaseEvent : EventCRTP<KeyReleaseEvent> {
private:
    int m_key;
    KeyModifiers m_mods;

    friend Game;

public:
    int key() const {
        return m_key;
    }

    KeyModifiers mods() const {
        return m_mods;
    }
};

struct CharInputEvent : EventCRTP<CharInputEvent> {
private:
    char32_t m_code;

    friend Game;

public:
    char32_t code() const {
        return m_code;
    }
};

struct DragAndDropEvent : EventCRTP<DragAndDropEvent> {
private:
    std::vector<std::string> m_paths;

    friend Game;

public:
    std::vector<std::string> const &paths() const {
        return m_paths;
    }
};

#define ZENO_FOREACH_EVENT_TYPE(_F) \
    /* X-macro pattern */ \
    _F(MouseClickEvent) \
    _F(MouseReleaseEvent) \
    _F(MouseMoveEvent) \
    _F(MouseHoverEvent) \
    _F(MouseWheelEvent) \
    _F(KeyPressEvent) \
    _F(KeyReleaseEvent) \
    _F(CharInputEvent) \
    _F(DragAndDropEvent) \
    /* end of X-macro */

}
